Skip to content
This repository was archived by the owner on Apr 3, 2024. It is now read-only.

Commit 3b97598

Browse files
fix: refuse to start if working dir is root dir (#381)
fixes: #377
1 parent 5c93c44 commit 3b97598

File tree

12 files changed

+147
-38
lines changed

12 files changed

+147
-38
lines changed

src/agent/config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ export type DebugAgentConfig = {
2323
export interface ResolvedDebugAgentConfig extends common.AuthenticationConfig {
2424
workingDirectory: string;
2525

26+
allowRootAsWorkingDirectory: boolean;
27+
2628
/**
2729
* A user specified way of identifying the service
2830
*/
@@ -174,6 +176,7 @@ export const defaultConfig: ResolvedDebugAgentConfig = {
174176
// FIXME(ofrobots): presently this is dependent what cwd() is at the time this
175177
// file is first required. We should make the default config static.
176178
workingDirectory: process.cwd(),
179+
allowRootAsWorkingDirectory: false,
177180
description: undefined,
178181
allowExpressions: false,
179182

src/agent/debuglet.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,20 @@ export class Debuglet extends EventEmitter {
299299
return;
300300
}
301301

302+
const workingDir = that.config.workingDirectory;
303+
// Don't continue if the working directory is a root directory
304+
// unless the user wants to force using the root directory
305+
if (!that.config.allowRootAsWorkingDirectory &&
306+
path.join(workingDir, '..') === workingDir) {
307+
const message = 'The working directory is a root directory. Disabling ' +
308+
'to avoid a scan of the entire filesystem for JavaScript files. ' +
309+
'Use config \allowRootAsWorkingDirectory` if you really want to ' +
310+
'do this.';
311+
that.logger.error(message);
312+
that.emit('initError', new Error(message));
313+
return;
314+
}
315+
302316
// TODO: Verify that it is fine for `id` to be undefined.
303317
let id: string|undefined;
304318
if (process.env.GAE_MINOR_VERSION) {
@@ -732,7 +746,6 @@ export class Debuglet extends EventEmitter {
732746
formatBreakpoints('Server breakpoints: ', updatedBreakpointMap));
733747
}
734748
breakpoints.forEach((breakpoint: stackdriver.Breakpoint) => {
735-
736749
// TODO: Address the case when `breakpoint.id` is `undefined`.
737750
if (!that.completedBreakpointMap[breakpoint.id as string] &&
738751
!that.activeBreakpointMap[breakpoint.id as string]) {

src/agent/io/sourcemapper.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -236,11 +236,11 @@ export class SourceMapper {
236236
// TODO: The `sourceMap.Position` type definition has a `column`
237237
// attribute and not a `col` attribute. Determine if the type
238238
// definition or this code is correct.
239-
column: (mappedPos as {} as {
240-
col: number
241-
}).col // SourceMapConsumer uses zero-based column
242-
// numbers which is the same as the
243-
// expected output
239+
column: (mappedPos as {} as {col: number}).col // SourceMapConsumer uses
240+
// zero-based column
241+
// numbers which is the
242+
// same as the expected
243+
// output
244244
};
245245
}
246246
}

src/agent/state/inspector-state.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,6 @@ class StateResolver {
174174
}
175175
}
176176
this.evaluatedExpressions[index2] = evaluated;
177-
178177
});
179178
}
180179
// The frames are resolved after the evaluated expressions so that

system-test/test-controller.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,5 +121,4 @@ describe('Controller', function() {
121121
// To be able to write the following test we need a service for adding a
122122
// breakpoint (need the debugger API). TODO.
123123
it('should update an active breakpoint');
124-
125124
});

system-test/test-e2e.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,6 @@ describe('@google-cloud/debug end-to-end behavior', () => {
223223
// TODO: Determine if the results parameter should be used.
224224
})
225225
.then((results: stackdriver.Breakpoint[]) => {
226-
227226
// Check the contents of the log, but keep the original breakpoint.
228227

229228
// TODO: This is never used. Determine if it should be used.

test/test-controller.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ const api = '/v2/controller';
3939
nock.disableNetConnect();
4040

4141
describe('Controller API', () => {
42-
4342
describe('register', () => {
4443
it('should get a debuggeeId', (done) => {
4544
const scope = nock(url).post(api + '/debuggees/register').reply(200, {
@@ -85,11 +84,9 @@ describe('Controller API', () => {
8584
done();
8685
});
8786
});
88-
8987
});
9088

9189
describe('listBreakpoints', () => {
92-
9390
// register before each test
9491
before((done) => {
9592
nock(url).post(api + '/debuggees/register').reply(200, {
@@ -268,7 +265,4 @@ describe('Controller API', () => {
268265
});
269266
});
270267
});
271-
272-
273-
274268
});

test/test-debuggee.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import {Debuggee} from '../src/debuggee';
2020
const agentVersion = `SomeName/client/SomeVersion`;
2121

2222
describe('Debuggee', () => {
23-
2423
it('should create a Debuggee instance on valid input', () => {
2524
const debuggee = new Debuggee({
2625
project: 'project',
@@ -62,5 +61,4 @@ describe('Debuggee', () => {
6261
});
6362
});
6463
});
65-
6664
});

test/test-debuglet.ts

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616

1717
import * as assert from 'assert';
18-
import * as rawFs from 'fs';
18+
import * as fs from 'fs';
1919
import * as _ from 'lodash';
2020
import * as path from 'path';
2121
import * as proxyquire from 'proxyquire';
@@ -28,7 +28,7 @@ import * as stackdriver from '../src/types/stackdriver';
2828

2929
DEFAULT_CONFIG.allowExpressions = true;
3030
DEFAULT_CONFIG.workingDirectory = path.join(__dirname, '..', '..');
31-
import {Debuglet, CachedPromise} from '../src/agent/debuglet';
31+
import {Debuglet, CachedPromise, FindFilesResult} from '../src/agent/debuglet';
3232
import {ScanResults} from '../src/agent/io/scanner';
3333
import * as dns from 'dns';
3434
import * as extend from 'extend';
@@ -814,6 +814,122 @@ describe('Debuglet', () => {
814814
debuglet.start();
815815
});
816816

817+
it('should by default error when workingDirectory is a root directory with a package.json',
818+
(done) => {
819+
const debug = new Debug({}, packageInfo);
820+
/*
821+
* `path.sep` represents a root directory on both Windows and Unix.
822+
* On Windows, `path.sep` resolves to the current drive.
823+
*
824+
* That is, after opening a command prompt in Windows, relative to the
825+
* drive C: and starting the Node REPL, the value of `path.sep`
826+
* represents `C:\\'.
827+
*
828+
* If `D:` is entered at the prompt to switch to the D: drive before
829+
* starting the Node REPL, `path.sep` represents `D:\\`.
830+
*/
831+
const root = path.sep;
832+
const mockedDebuglet = proxyquire('../src/agent/debuglet', {
833+
/*
834+
* Mock the 'fs' module to verify that if the root directory is used,
835+
* and the root directory is reported to contain a `package.json`
836+
* file, then the agent still produces an `initError` when the
837+
* working directory is the root directory.
838+
*/
839+
fs: {
840+
stat:
841+
(filepath: string|Buffer,
842+
cb: (err: Error|null, stats: {}) => void) => {
843+
if (filepath === path.join(root, 'package.json')) {
844+
// The key point is that looking for `package.json` in the
845+
// root directory does not cause an error.
846+
return cb(null, {});
847+
}
848+
fs.stat(filepath, cb);
849+
}
850+
}
851+
});
852+
const config = extend({}, defaultConfig, {workingDirectory: root});
853+
const debuglet = new mockedDebuglet.Debuglet(debug, config);
854+
let text = '';
855+
debuglet.logger.error = (str: string) => {
856+
text += str;
857+
};
858+
859+
debuglet.on('initError', (err: Error) => {
860+
const errorMessage = 'The working directory is a root ' +
861+
'directory. Disabling to avoid a scan of the entire filesystem ' +
862+
'for JavaScript files. Use config \allowRootAsWorkingDirectory` ' +
863+
'if you really want to do this.';
864+
assert.ok(err);
865+
assert.strictEqual(err.message, errorMessage);
866+
assert.ok(text.includes(errorMessage));
867+
done();
868+
});
869+
870+
debuglet.once('started', () => {
871+
assert.fail(
872+
'Should not start if workingDirectory is a root directory');
873+
});
874+
875+
debuglet.start();
876+
});
877+
878+
it('should be able to force the workingDirectory to be a root directory',
879+
(done) => {
880+
const root = path.sep;
881+
// Act like the root directory contains a `package.json` file
882+
const mockedDebuglet = proxyquire('../src/agent/debuglet', {
883+
fs: {
884+
stat:
885+
(filepath: string|Buffer,
886+
cb: (err: Error|null, stats: {}) => void) => {
887+
if (filepath === path.join(root, 'package.json')) {
888+
return cb(null, {});
889+
}
890+
fs.stat(filepath, cb);
891+
}
892+
}
893+
});
894+
895+
// Don't actually scan the entire filesystem. Act like the filesystem
896+
// is empty.
897+
mockedDebuglet.Debuglet.findFiles =
898+
(shouldHash: boolean, baseDir: string):
899+
Promise<FindFilesResult> => {
900+
assert.strictEqual(baseDir, root);
901+
return Promise.resolve({
902+
jsStats: {},
903+
mapFiles: [],
904+
errors: new Map<string, Error>()
905+
});
906+
};
907+
908+
// Act like the debuglet can get a project id
909+
mockedDebuglet.Debuglet.getProjectId = () => 'some-project-id';
910+
911+
// No need to restore `findFiles` and `getProjectId` because we are
912+
// modifying a mocked version of `Debuglet` not `Debuglet` itself.
913+
914+
const config = extend(
915+
{}, defaultConfig,
916+
{workingDirectory: root, allowRootAsWorkingDirectory: true});
917+
const debug = new Debug({}, packageInfo);
918+
const debuglet = new mockedDebuglet.Debuglet(debug, config);
919+
920+
debuglet.on('initError', (err: Error) => {
921+
assert.ifError(err);
922+
done();
923+
});
924+
925+
debuglet.once('started', () => {
926+
debuglet.stop();
927+
done();
928+
});
929+
930+
debuglet.start();
931+
});
932+
817933
it('should register successfully otherwise', (done) => {
818934
const debug = new Debug(
819935
{projectId: 'fake-project', credentials: fakeCredentials},

test/test-module.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,4 @@ describe('Debug module', () => {
3737
module.start();
3838
});
3939
});
40-
4140
});

0 commit comments

Comments
 (0)