@nomos-arc/arc 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +10 -0
- package/.nomos-config.json +5 -0
- package/CLAUDE.md +108 -0
- package/LICENSE +190 -0
- package/README.md +569 -0
- package/dist/cli.js +21120 -0
- package/docs/auth/googel_plan.yaml +1093 -0
- package/docs/auth/google_task.md +235 -0
- package/docs/auth/hardened_blueprint.yaml +1658 -0
- package/docs/auth/red_team_report.yaml +336 -0
- package/docs/auth/session_state.yaml +162 -0
- package/docs/certificate/cer_enhance_plan.md +605 -0
- package/docs/certificate/certificate_report.md +338 -0
- package/docs/dev_overview.md +419 -0
- package/docs/feature_assessment.md +156 -0
- package/docs/how_it_works.md +78 -0
- package/docs/infrastructure/map.md +867 -0
- package/docs/init/master_plan.md +3581 -0
- package/docs/init/red_team_report.md +215 -0
- package/docs/init/report_phase_1a.md +304 -0
- package/docs/integrity-gate/enhance_drift.md +703 -0
- package/docs/integrity-gate/overview.md +108 -0
- package/docs/management/manger-task.md +99 -0
- package/docs/management/scafffold.md +76 -0
- package/docs/map/ATOMIC_BLUEPRINT.md +1349 -0
- package/docs/map/RED_TEAM_REPORT.md +159 -0
- package/docs/map/map_task.md +147 -0
- package/docs/map/semantic_graph_task.md +792 -0
- package/docs/map/semantic_master_plan.md +705 -0
- package/docs/phase7/TEAM_RED.md +249 -0
- package/docs/phase7/plan.md +1682 -0
- package/docs/phase7/task.md +275 -0
- package/docs/prompts/USAGE.md +312 -0
- package/docs/prompts/architect.md +165 -0
- package/docs/prompts/executer.md +190 -0
- package/docs/prompts/hardener.md +190 -0
- package/docs/prompts/red_team.md +146 -0
- package/docs/verification/goveranance-overview.md +396 -0
- package/docs/verification/governance-overview.md +245 -0
- package/docs/verification/verification-arc-ar.md +560 -0
- package/docs/verification/verification-architecture.md +560 -0
- package/docs/very_next.md +52 -0
- package/docs/whitepaper.md +89 -0
- package/overview.md +1469 -0
- package/package.json +63 -0
- package/src/adapters/__tests__/git.test.ts +296 -0
- package/src/adapters/__tests__/stdio.test.ts +70 -0
- package/src/adapters/git.ts +226 -0
- package/src/adapters/pty.ts +159 -0
- package/src/adapters/stdio.ts +113 -0
- package/src/cli.ts +83 -0
- package/src/commands/apply.ts +47 -0
- package/src/commands/auth.ts +301 -0
- package/src/commands/certificate.ts +89 -0
- package/src/commands/discard.ts +24 -0
- package/src/commands/drift.ts +116 -0
- package/src/commands/index.ts +78 -0
- package/src/commands/init.ts +121 -0
- package/src/commands/list.ts +75 -0
- package/src/commands/map.ts +55 -0
- package/src/commands/plan.ts +30 -0
- package/src/commands/review.ts +58 -0
- package/src/commands/run.ts +63 -0
- package/src/commands/search.ts +147 -0
- package/src/commands/show.ts +63 -0
- package/src/commands/status.ts +59 -0
- package/src/core/__tests__/budget.test.ts +213 -0
- package/src/core/__tests__/certificate.test.ts +385 -0
- package/src/core/__tests__/config.test.ts +191 -0
- package/src/core/__tests__/preflight.test.ts +24 -0
- package/src/core/__tests__/prompt.test.ts +358 -0
- package/src/core/__tests__/review.test.ts +161 -0
- package/src/core/__tests__/state.test.ts +362 -0
- package/src/core/auth/__tests__/manager.test.ts +166 -0
- package/src/core/auth/__tests__/server.test.ts +220 -0
- package/src/core/auth/gcp-projects.ts +160 -0
- package/src/core/auth/manager.ts +114 -0
- package/src/core/auth/server.ts +141 -0
- package/src/core/budget.ts +119 -0
- package/src/core/certificate.ts +502 -0
- package/src/core/config.ts +212 -0
- package/src/core/errors.ts +54 -0
- package/src/core/factory.ts +49 -0
- package/src/core/graph/__tests__/builder.test.ts +272 -0
- package/src/core/graph/__tests__/contract-writer.test.ts +175 -0
- package/src/core/graph/__tests__/enricher.test.ts +299 -0
- package/src/core/graph/__tests__/parser.test.ts +200 -0
- package/src/core/graph/__tests__/pipeline.test.ts +202 -0
- package/src/core/graph/__tests__/renderer.test.ts +128 -0
- package/src/core/graph/__tests__/resolver.test.ts +185 -0
- package/src/core/graph/__tests__/scanner.test.ts +231 -0
- package/src/core/graph/__tests__/show.test.ts +134 -0
- package/src/core/graph/builder.ts +303 -0
- package/src/core/graph/constraints.ts +94 -0
- package/src/core/graph/contract-writer.ts +93 -0
- package/src/core/graph/drift/__tests__/classifier.test.ts +215 -0
- package/src/core/graph/drift/__tests__/comparator.test.ts +335 -0
- package/src/core/graph/drift/__tests__/drift.test.ts +453 -0
- package/src/core/graph/drift/__tests__/reporter.test.ts +203 -0
- package/src/core/graph/drift/classifier.ts +165 -0
- package/src/core/graph/drift/comparator.ts +205 -0
- package/src/core/graph/drift/reporter.ts +77 -0
- package/src/core/graph/enricher.ts +251 -0
- package/src/core/graph/grammar-paths.ts +30 -0
- package/src/core/graph/html-template.ts +493 -0
- package/src/core/graph/map-schema.ts +137 -0
- package/src/core/graph/parser.ts +336 -0
- package/src/core/graph/pipeline.ts +209 -0
- package/src/core/graph/renderer.ts +92 -0
- package/src/core/graph/resolver.ts +195 -0
- package/src/core/graph/scanner.ts +145 -0
- package/src/core/logger.ts +46 -0
- package/src/core/orchestrator.ts +792 -0
- package/src/core/plan-file-manager.ts +66 -0
- package/src/core/preflight.ts +64 -0
- package/src/core/prompt.ts +173 -0
- package/src/core/review.ts +95 -0
- package/src/core/state.ts +294 -0
- package/src/core/worktree-coordinator.ts +77 -0
- package/src/search/__tests__/chunk-extractor.test.ts +339 -0
- package/src/search/__tests__/embedder-auth.test.ts +124 -0
- package/src/search/__tests__/embedder.test.ts +267 -0
- package/src/search/__tests__/graph-enricher.test.ts +178 -0
- package/src/search/__tests__/indexer.test.ts +518 -0
- package/src/search/__tests__/integration.test.ts +649 -0
- package/src/search/__tests__/query-engine.test.ts +334 -0
- package/src/search/__tests__/similarity.test.ts +78 -0
- package/src/search/__tests__/vector-store.test.ts +281 -0
- package/src/search/chunk-extractor.ts +167 -0
- package/src/search/embedder.ts +209 -0
- package/src/search/graph-enricher.ts +95 -0
- package/src/search/indexer.ts +483 -0
- package/src/search/lexical-searcher.ts +190 -0
- package/src/search/query-engine.ts +225 -0
- package/src/search/vector-store.ts +311 -0
- package/src/types/index.ts +572 -0
- package/src/utils/__tests__/ansi.test.ts +54 -0
- package/src/utils/__tests__/frontmatter.test.ts +79 -0
- package/src/utils/__tests__/sanitize.test.ts +229 -0
- package/src/utils/ansi.ts +19 -0
- package/src/utils/context.ts +44 -0
- package/src/utils/frontmatter.ts +27 -0
- package/src/utils/sanitize.ts +78 -0
- package/test/e2e/lifecycle.test.ts +330 -0
- package/test/fixtures/mock-planner-hang.ts +5 -0
- package/test/fixtures/mock-planner.ts +26 -0
- package/test/fixtures/mock-reviewer-bad.ts +8 -0
- package/test/fixtures/mock-reviewer-retry.ts +34 -0
- package/test/fixtures/mock-reviewer.ts +18 -0
- package/test/fixtures/sample-project/src/circular-a.ts +6 -0
- package/test/fixtures/sample-project/src/circular-b.ts +6 -0
- package/test/fixtures/sample-project/src/config.ts +15 -0
- package/test/fixtures/sample-project/src/main.ts +19 -0
- package/test/fixtures/sample-project/src/services/product-service.ts +20 -0
- package/test/fixtures/sample-project/src/services/user-service.ts +18 -0
- package/test/fixtures/sample-project/src/types.ts +14 -0
- package/test/fixtures/sample-project/src/utils/index.ts +14 -0
- package/test/fixtures/sample-project/src/utils/validate.ts +12 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +12 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
audit:
|
|
2
|
+
task_id: "auth-oauth-login"
|
|
3
|
+
task_title: "Frictionless OAuth 2.0 Google Login for arc CLI"
|
|
4
|
+
audited_at: "2026-04-06T12:00:00Z"
|
|
5
|
+
|
|
6
|
+
verdict: "REVISE"
|
|
7
|
+
system_integrity_score: 62
|
|
8
|
+
total_findings: 9
|
|
9
|
+
findings_by_severity: { critical: 1, high: 3, medium: 4, low: 1 }
|
|
10
|
+
|
|
11
|
+
findings:
|
|
12
|
+
- finding_id: "F-001"
|
|
13
|
+
domain: "security"
|
|
14
|
+
severity: "critical"
|
|
15
|
+
step_id: "3.1"
|
|
16
|
+
title: "Client secret exposed via CLI flag, shell history, and process list"
|
|
17
|
+
description: |
|
|
18
|
+
The plan specifies `--client-secret <secret>` as a Commander.js option for
|
|
19
|
+
`arc auth login`. Any value passed via CLI flag is:
|
|
20
|
+
1. Stored in shell history (~/.bash_history, ~/.zsh_history)
|
|
21
|
+
2. Visible in `ps aux` process list to all users on the system
|
|
22
|
+
3. Potentially logged by corporate endpoint monitoring tools
|
|
23
|
+
client_secret is a long-lived credential that cannot be rotated easily.
|
|
24
|
+
attack_vector: |
|
|
25
|
+
Any user or process with read access to shell history or /proc can
|
|
26
|
+
extract the client_secret. On shared servers this is trivial:
|
|
27
|
+
`grep 'client-secret' ~/.bash_history`
|
|
28
|
+
evidence: |
|
|
29
|
+
Step 3.1, lines 397-398: `.option('--client-id <id>', ...)` and
|
|
30
|
+
`.option('--client-secret <secret>', ...)`. No mention of stdin/prompt
|
|
31
|
+
fallback or env var alternative for the secret.
|
|
32
|
+
recommendation: |
|
|
33
|
+
Remove `--client-secret` CLI flag entirely. Instead:
|
|
34
|
+
1. Primary: Read from config file (`auth.client_secret` in .nomos-config.json)
|
|
35
|
+
2. Fallback: Read from env var `NOMOS_GOOGLE_CLIENT_SECRET`
|
|
36
|
+
3. Last resort: Interactive prompt via `readline` (masked input)
|
|
37
|
+
Keep `--client-id` as a flag (not sensitive). Update step 3.1 description
|
|
38
|
+
and the priority resolution logic accordingly.
|
|
39
|
+
|
|
40
|
+
- finding_id: "F-002"
|
|
41
|
+
domain: "security"
|
|
42
|
+
severity: "high"
|
|
43
|
+
step_id: "2.2"
|
|
44
|
+
title: "Loopback server has no CSRF protection on OAuth callback"
|
|
45
|
+
description: |
|
|
46
|
+
The loopback server at step 2.2 accepts any GET request with a `code`
|
|
47
|
+
parameter and exchanges it for tokens. There is no `state` parameter
|
|
48
|
+
validation. RFC 6749 Section 10.12 requires a `state` parameter to
|
|
49
|
+
prevent CSRF attacks on the OAuth callback.
|
|
50
|
+
attack_vector: |
|
|
51
|
+
An attacker who knows the user is running `arc auth login` can craft a
|
|
52
|
+
URL like `http://localhost:3000/?code=ATTACKER_CODE` and trick the user
|
|
53
|
+
into visiting it (e.g., via a malicious page with an img tag). The CLI
|
|
54
|
+
would exchange the attacker's code, potentially linking the user's CLI
|
|
55
|
+
to an attacker-controlled Google account.
|
|
56
|
+
evidence: |
|
|
57
|
+
Step 2.2, lines 328-330: Callback handler parses `code` from `req.url`
|
|
58
|
+
but makes no mention of generating, sending, or validating a `state`
|
|
59
|
+
parameter. The `generateAuthUrl` call (lines 316-320) does not include
|
|
60
|
+
a `state` field.
|
|
61
|
+
recommendation: |
|
|
62
|
+
In step 2.2, add to the auth URL generation:
|
|
63
|
+
```
|
|
64
|
+
const state = crypto.randomBytes(32).toString('hex');
|
|
65
|
+
oauth2Client.generateAuthUrl({ ..., state });
|
|
66
|
+
```
|
|
67
|
+
In the callback handler, verify `req.url` contains the matching `state`
|
|
68
|
+
value before exchanging the code. Reject with 403 if mismatched.
|
|
69
|
+
Add `crypto` to the imports list.
|
|
70
|
+
|
|
71
|
+
- finding_id: "F-003"
|
|
72
|
+
domain: "architecture"
|
|
73
|
+
severity: "high"
|
|
74
|
+
step_id: "4.1"
|
|
75
|
+
title: "OAuth access_token used as API key may not authenticate Gemini API"
|
|
76
|
+
description: |
|
|
77
|
+
The plan passes the OAuth access_token directly to `new GoogleGenerativeAI(key)`
|
|
78
|
+
as if it were an API key. The @google/generative-ai SDK (^0.24.1) uses this
|
|
79
|
+
value as `x-goog-api-key` header. OAuth access tokens are Bearer tokens and
|
|
80
|
+
must be sent as `Authorization: Bearer <token>`. These are fundamentally
|
|
81
|
+
different authentication mechanisms.
|
|
82
|
+
attack_vector: |
|
|
83
|
+
Not a security attack — this is a functional failure. The entire OAuth
|
|
84
|
+
credential chain (steps 4.1, 4.2, 4.3, 4.4, 4.5, 4.6) is non-functional
|
|
85
|
+
if the SDK rejects bearer tokens passed as API keys.
|
|
86
|
+
evidence: |
|
|
87
|
+
Step 4.1, lines 531-533: `return new Embedder(config, logger, token)` where
|
|
88
|
+
`token` is from `authManager.getAccessToken()`. The Embedder constructor
|
|
89
|
+
(src/search/embedder.ts:36) passes this to `new GoogleGenerativeAI(key)`.
|
|
90
|
+
The plan acknowledges this risk in `risk_assessment.failure_scenarios[0]`
|
|
91
|
+
(lines 1033-1041) but proposes no concrete fallback step — only a vague
|
|
92
|
+
"fall back to custom Authorization header injection."
|
|
93
|
+
recommendation: |
|
|
94
|
+
Add a concrete step between 2.1 and 4.1: a spike/validation step that
|
|
95
|
+
tests whether `new GoogleGenerativeAI(accessToken)` succeeds with an
|
|
96
|
+
OAuth token. If it fails, the plan MUST include the alternative path
|
|
97
|
+
(custom request headers or `GoogleAuth` credential injection) as actual
|
|
98
|
+
steps, not just a mitigation note. Without this, steps 4.1-4.6 are
|
|
99
|
+
speculative.
|
|
100
|
+
|
|
101
|
+
- finding_id: "F-004"
|
|
102
|
+
domain: "ai_divergence"
|
|
103
|
+
severity: "high"
|
|
104
|
+
step_id: "3.1"
|
|
105
|
+
title: "Action handlers contain `...` placeholders — executor will improvise"
|
|
106
|
+
description: |
|
|
107
|
+
The code blocks for `arc auth login`, `arc auth logout`, and
|
|
108
|
+
`arc auth status` actions all contain `.action(async (opts) => { ... })`
|
|
109
|
+
with literal `...` as the implementation body. The detailed flow is
|
|
110
|
+
described in prose below the code block, but an AI executor seeing
|
|
111
|
+
`{ ... }` in a code template is likely to either:
|
|
112
|
+
1. Generate a minimal stub that doesn't match the prose spec
|
|
113
|
+
2. Invent its own flow that diverges from the described steps
|
|
114
|
+
evidence: |
|
|
115
|
+
Step 3.1, line 398: `.action(async (opts) => { ... });`
|
|
116
|
+
Step 3.1, line 404: `.action(async () => { ... });`
|
|
117
|
+
Step 3.1, line 410: `.action(async () => { ... });`
|
|
118
|
+
The actual implementation details are in prose paragraphs starting
|
|
119
|
+
at lines 414, 426, and 431 — separated from the code template.
|
|
120
|
+
recommendation: |
|
|
121
|
+
Replace `{ ... }` with full implementation code in the code block,
|
|
122
|
+
or at minimum replace with explicit pseudocode comments:
|
|
123
|
+
```typescript
|
|
124
|
+
.action(async (opts) => {
|
|
125
|
+
// 1. loadConfig()
|
|
126
|
+
// 2. resolve clientId/clientSecret (flag > config > throw)
|
|
127
|
+
// 3. new OAuth2Client({ clientId, clientSecret })
|
|
128
|
+
// 4. new AuthManager(config.auth, logger)
|
|
129
|
+
// 5. startLoopbackServer(...)
|
|
130
|
+
// 6. authManager.saveCredentials(tokens)
|
|
131
|
+
// 7. console.log success
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
- finding_id: "F-005"
|
|
136
|
+
domain: "ai_divergence"
|
|
137
|
+
severity: "medium"
|
|
138
|
+
step_id: "4.6"
|
|
139
|
+
title: "Single step modifies 3 files with 'same pattern' shorthand"
|
|
140
|
+
description: |
|
|
141
|
+
Step 4.6 modifies src/commands/index.ts, src/commands/search.ts, and
|
|
142
|
+
src/commands/map.ts. Only index.ts gets detailed code changes. The other
|
|
143
|
+
two files get "Same pattern" one-liners. An AI executor may:
|
|
144
|
+
1. Apply changes only to index.ts and skip the other two
|
|
145
|
+
2. Apply literally identical code without adapting to each file's
|
|
146
|
+
specific constructor (QueryEngine vs MapPipeline)
|
|
147
|
+
evidence: |
|
|
148
|
+
Step 4.6, lines 816-818: "Also update src/commands/search.ts: Same pattern"
|
|
149
|
+
Step 4.6, lines 820-822: "Also update src/commands/map.ts: Same pattern"
|
|
150
|
+
Meanwhile src/commands/search.ts instantiates `QueryEngine` (not SearchIndexer)
|
|
151
|
+
and src/commands/map.ts instantiates `MapPipeline` with a different constructor
|
|
152
|
+
signature: `new MapPipeline(config, projectRoot, logger)` (config first,
|
|
153
|
+
projectRoot second — reversed from SearchIndexer).
|
|
154
|
+
recommendation: |
|
|
155
|
+
Split step 4.6 into three separate steps (4.6a, 4.6b, 4.6c) — one per
|
|
156
|
+
file — each with explicit code showing the exact constructor call change.
|
|
157
|
+
At minimum, show the MapPipeline change explicitly since its constructor
|
|
158
|
+
parameter order differs from SearchIndexer.
|
|
159
|
+
|
|
160
|
+
- finding_id: "F-006"
|
|
161
|
+
domain: "ai_divergence"
|
|
162
|
+
severity: "medium"
|
|
163
|
+
step_id: "5.1"
|
|
164
|
+
title: "Ambiguous action — 'No modification expected unless a gap is found'"
|
|
165
|
+
description: |
|
|
166
|
+
Step 5.1 says the action is MODIFY but the description concludes with
|
|
167
|
+
"No modification expected unless a gap is found." This gives the AI
|
|
168
|
+
executor discretion to either modify or not modify the file based on
|
|
169
|
+
its own judgment. The executor could:
|
|
170
|
+
1. Skip the step entirely (interpreting "no change needed")
|
|
171
|
+
2. Add unnecessary patterns to ALWAYS_DENY
|
|
172
|
+
3. Misidentify a non-gap as a gap and make harmful changes
|
|
173
|
+
evidence: |
|
|
174
|
+
Step 5.1, lines 853-863: "No code change needed if the existing patterns
|
|
175
|
+
already cover... Action: Read the file, verify coverage, document finding.
|
|
176
|
+
No modification expected unless a gap is found."
|
|
177
|
+
Current ALWAYS_DENY (src/utils/sanitize.ts:57-61) already covers
|
|
178
|
+
`GOOGLE_.*?(KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL)` which matches
|
|
179
|
+
Google OAuth env vars.
|
|
180
|
+
recommendation: |
|
|
181
|
+
Change step 5.1 action from "MODIFY" to "VERIFY". Remove the conditional
|
|
182
|
+
language. State explicitly: "Verify that the regex
|
|
183
|
+
`GOOGLE_.*?(KEY|SECRET|TOKEN|...)` in ALWAYS_DENY covers Google OAuth
|
|
184
|
+
env vars. Expected result: no changes needed. If the regex does NOT
|
|
185
|
+
match `GOOGLE_CLIENT_SECRET`, add it and report the gap."
|
|
186
|
+
|
|
187
|
+
- finding_id: "F-007"
|
|
188
|
+
domain: "rollback"
|
|
189
|
+
severity: "medium"
|
|
190
|
+
step_id: "4.3"
|
|
191
|
+
title: "Sync-to-async accessor change breaks rollback of downstream steps"
|
|
192
|
+
description: |
|
|
193
|
+
Step 4.3 changes `private get embedder(): Embedder` (synchronous accessor)
|
|
194
|
+
to `private async getEmbedder(): Promise<Embedder>` (async method). This
|
|
195
|
+
changes every call site from `this.embedder.X()` to
|
|
196
|
+
`(await this.getEmbedder()).X()`. If step 4.3 is rolled back but step 4.6
|
|
197
|
+
was already applied, `src/commands/index.ts` will pass `authManager` to a
|
|
198
|
+
constructor that no longer accepts it.
|
|
199
|
+
More critically, if 4.3 is partially applied (accessor changed but not all
|
|
200
|
+
call sites updated), the file won't compile.
|
|
201
|
+
evidence: |
|
|
202
|
+
Step 4.3 depends_on: ["4.1"]. Step 4.6 depends_on: ["4.3", "4.4", "4.5"].
|
|
203
|
+
Rollback of 4.3 says "Revert the lazy accessor and constructor changes"
|
|
204
|
+
but does not mention that 4.6 must also be reverted first.
|
|
205
|
+
recommendation: |
|
|
206
|
+
Add explicit rollback ordering: "Rollback 4.6 BEFORE rolling back 4.3."
|
|
207
|
+
Add a `rollback_depends_on: ["4.6"]` field or equivalent note to step 4.3.
|
|
208
|
+
Same applies to steps 4.1, 4.2, 4.4, 4.5.
|
|
209
|
+
|
|
210
|
+
- finding_id: "F-008"
|
|
211
|
+
domain: "architecture"
|
|
212
|
+
severity: "medium"
|
|
213
|
+
step_id: "2.1"
|
|
214
|
+
title: "loadCredentials() uses synchronous fs.readFileSync in async-capable class"
|
|
215
|
+
description: |
|
|
216
|
+
Step 2.1 specifies `loadCredentials(): AuthCredentials | null` as a
|
|
217
|
+
synchronous method using `fs.readFileSync`. This blocks the Node.js event
|
|
218
|
+
loop during file I/O. While credentials.json is small, this breaks the
|
|
219
|
+
project's pattern — the codebase consistently uses async fs operations
|
|
220
|
+
(e.g., indexer.ts writeMeta uses `fs.writeFile` from `node:fs/promises`).
|
|
221
|
+
The method is called from `isLoggedIn()` (also sync) and
|
|
222
|
+
`getAuthenticatedClient()` (async), so making it async would only require
|
|
223
|
+
updating `getAuthenticatedClient`.
|
|
224
|
+
evidence: |
|
|
225
|
+
Step 2.1, lines 242-244: "Synchronous read. Return null if file doesn't
|
|
226
|
+
exist or is invalid JSON."
|
|
227
|
+
Contrast with src/search/indexer.ts lines 439-444 which uses async
|
|
228
|
+
`fs.writeFile` + `fs.rename` for atomic writes.
|
|
229
|
+
`isLoggedIn()` (line 256) calls `loadCredentials()` synchronously.
|
|
230
|
+
recommendation: |
|
|
231
|
+
Accept the sync design for `loadCredentials()` and `isLoggedIn()` since
|
|
232
|
+
they are called in non-hot-path contexts (CLI startup, not per-request).
|
|
233
|
+
However, add a negative constraint: "DO NOT use readFileSync in any
|
|
234
|
+
method that could be called in a loop or hot path." This is a MEDIUM
|
|
235
|
+
concern — not blocking, but worth noting for the planner.
|
|
236
|
+
|
|
237
|
+
- finding_id: "F-009"
|
|
238
|
+
domain: "security"
|
|
239
|
+
severity: "low"
|
|
240
|
+
step_id: "2.2"
|
|
241
|
+
title: "Success HTML response could be used for phishing if server lingers"
|
|
242
|
+
description: |
|
|
243
|
+
The loopback server responds with a success HTML page. If the cleanup
|
|
244
|
+
is delayed or the server is somehow kept alive, a local attacker could
|
|
245
|
+
serve content on localhost:3000. This is low risk because the server
|
|
246
|
+
has a 120s timeout and socket tracking.
|
|
247
|
+
attack_vector: |
|
|
248
|
+
Theoretical: if socket cleanup fails, server continues to serve the
|
|
249
|
+
success HTML page, which could be replaced by a MITM on localhost
|
|
250
|
+
(extremely unlikely on modern systems).
|
|
251
|
+
evidence: |
|
|
252
|
+
Step 2.2, lines 331-332: "Respond with success HTML: simple page saying
|
|
253
|
+
'Login successful! You can close this tab.'"
|
|
254
|
+
Timeout: 120 seconds (line 334).
|
|
255
|
+
recommendation: |
|
|
256
|
+
Ensure the success response includes `Connection: close` header and
|
|
257
|
+
that `server.close()` is called immediately after sending the response,
|
|
258
|
+
not just on timeout. The plan describes this at line 343-344 but should
|
|
259
|
+
make it explicit in the code template.
|
|
260
|
+
|
|
261
|
+
hardening_instructions:
|
|
262
|
+
- step_id: "3.1"
|
|
263
|
+
instruction: "Remove --client-secret CLI flag. Support config file and env var NOMOS_GOOGLE_CLIENT_SECRET only. Add interactive masked prompt as last-resort fallback."
|
|
264
|
+
reason: "CLI flags expose secrets in shell history and process lists."
|
|
265
|
+
priority: "critical"
|
|
266
|
+
|
|
267
|
+
- step_id: "2.2"
|
|
268
|
+
instruction: "Add cryptographic state parameter to OAuth flow. Generate with crypto.randomBytes(32), pass in generateAuthUrl, validate in callback handler before token exchange."
|
|
269
|
+
reason: "RFC 6749 Section 10.12 requires state parameter to prevent CSRF on OAuth callbacks."
|
|
270
|
+
priority: "high"
|
|
271
|
+
|
|
272
|
+
- step_id: "4.1"
|
|
273
|
+
instruction: "Add a validation step before 4.1 that tests GoogleGenerativeAI SDK with an OAuth access_token. If it fails, provide concrete alternative steps using Authorization header injection."
|
|
274
|
+
reason: "The entire credential chain (6 steps) depends on an unverified assumption about SDK behavior."
|
|
275
|
+
priority: "high"
|
|
276
|
+
|
|
277
|
+
- step_id: "3.1"
|
|
278
|
+
instruction: "Replace `{ ... }` placeholders in code blocks with full implementation or detailed pseudocode comments."
|
|
279
|
+
reason: "Reduces AI executor improvisation risk."
|
|
280
|
+
priority: "high"
|
|
281
|
+
|
|
282
|
+
- step_id: "4.6"
|
|
283
|
+
instruction: "Split into 3 separate steps with explicit code for each file, especially showing MapPipeline's different constructor parameter order."
|
|
284
|
+
reason: "Prevents executor from applying incorrect 'same pattern' changes."
|
|
285
|
+
priority: "medium"
|
|
286
|
+
|
|
287
|
+
- step_id: "5.1"
|
|
288
|
+
instruction: "Change action from MODIFY to VERIFY. Remove conditional modification language."
|
|
289
|
+
reason: "Removes ambiguity about whether the executor should change the file."
|
|
290
|
+
priority: "medium"
|
|
291
|
+
|
|
292
|
+
negative_constraints:
|
|
293
|
+
- "DO NOT pass client_secret via CLI flags — it leaks to shell history and process lists"
|
|
294
|
+
- "DO NOT exchange OAuth authorization codes without validating the state parameter"
|
|
295
|
+
- "DO NOT modify src/core/state.ts — it manages task state and is unrelated to auth"
|
|
296
|
+
- "DO NOT modify src/core/orchestrator.ts — orchestration loop must remain unchanged"
|
|
297
|
+
- "DO NOT modify src/core/budget.ts — budget tracking is unrelated to auth"
|
|
298
|
+
- "DO NOT modify src/search/vector-store.ts or src/search/chunk-extractor.ts"
|
|
299
|
+
- "DO NOT add new dependencies beyond google-auth-library — open is already installed"
|
|
300
|
+
- "DO NOT store tokens in environment variables — use file-based storage only"
|
|
301
|
+
- "DO NOT log access_token, refresh_token, or client_secret values at any log level"
|
|
302
|
+
- "DO NOT use readFileSync in any method that could be called in a loop or hot path"
|
|
303
|
+
|
|
304
|
+
rollback_assessment:
|
|
305
|
+
is_fully_reversible: false
|
|
306
|
+
weak_points: ["4.3", "4.4", "4.5"]
|
|
307
|
+
cascade_risks:
|
|
308
|
+
- "Steps 4.3-4.6 form a tight dependency chain. Rolling back 4.3 (sync-to-async accessor in indexer.ts) without also rolling back 4.6 (command wiring) leaves index.ts passing authManager to a constructor that doesn't accept it. Rollback must proceed in reverse order: 4.6 -> 4.5 -> 4.4 -> 4.3."
|
|
309
|
+
- "Step 1.1 (npm install) modifies package-lock.json. npm uninstall may not produce a byte-identical package-lock.json, creating noise in git diff."
|
|
310
|
+
- "If step 6.2 (test suite) discovers failures after steps 4.1-4.6 are applied, selective rollback of individual steps is unsafe — all of Phase 4 must be rolled back together."
|
|
311
|
+
|
|
312
|
+
summary:
|
|
313
|
+
strengths:
|
|
314
|
+
- "Detailed step-by-step with explicit code blocks, file paths, and line numbers"
|
|
315
|
+
- "Additive factory method pattern preserves backward compatibility"
|
|
316
|
+
- "Clear dependency graph with can_parallel flags"
|
|
317
|
+
- "Comprehensive risk assessment with failure scenarios and mitigations"
|
|
318
|
+
- "Fragile zone identification correctly protects state.ts, orchestrator.ts, budget.ts"
|
|
319
|
+
- "Credential chain priority (env var -> OAuth -> error) is well-designed"
|
|
320
|
+
- "Socket tracking and timeout on loopback server prevents dangling processes"
|
|
321
|
+
critical_gaps:
|
|
322
|
+
- "Client secret exposed via CLI flag (F-001) — CRITICAL security issue"
|
|
323
|
+
- "No OAuth state parameter for CSRF protection (F-002)"
|
|
324
|
+
- "Unverified assumption that OAuth tokens work as GoogleGenerativeAI API keys (F-003)"
|
|
325
|
+
- "Placeholder code blocks risk AI executor divergence (F-004)"
|
|
326
|
+
recommendation: |
|
|
327
|
+
The plan is well-structured and follows existing codebase conventions closely.
|
|
328
|
+
However, it must be revised before execution due to one critical and three high
|
|
329
|
+
findings. The most urgent fix is removing the --client-secret CLI flag (F-001)
|
|
330
|
+
and replacing it with config/env/prompt-based secret injection. Second, add
|
|
331
|
+
OAuth state parameter validation to the loopback server (F-002) per RFC 6749.
|
|
332
|
+
Third, add a concrete validation step for the OAuth-token-as-API-key assumption
|
|
333
|
+
(F-003) — without this, 6 steps in Phase 4 are built on speculation. Finally,
|
|
334
|
+
replace the `{ ... }` placeholders in step 3.1 with actual implementation code
|
|
335
|
+
to prevent executor divergence (F-004). With these four fixes, the plan moves
|
|
336
|
+
from REVISE to APPROVE_WITH_NOTES.
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
execution_report:
|
|
2
|
+
task_id: "auth-oauth-login"
|
|
3
|
+
task_title: "Frictionless OAuth 2.0 Google Login for arc CLI"
|
|
4
|
+
phase_executed: 6
|
|
5
|
+
session_id: "2026-04-06T10:27:00Z"
|
|
6
|
+
status: "SUCCESS"
|
|
7
|
+
|
|
8
|
+
pre_execution_gates:
|
|
9
|
+
blueprint_integrity: "PASSED"
|
|
10
|
+
phase_exists: "PASSED"
|
|
11
|
+
dependencies_satisfied: "PASSED"
|
|
12
|
+
idempotency_check: "PASSED (no phase 6 steps in prior state)"
|
|
13
|
+
gate_result: "ALL_PASSED"
|
|
14
|
+
abort_reason: ""
|
|
15
|
+
|
|
16
|
+
steps_log:
|
|
17
|
+
- step_id: "6.1"
|
|
18
|
+
status: "SUCCESS"
|
|
19
|
+
pre_condition: "PASSED"
|
|
20
|
+
constraints_checked: true
|
|
21
|
+
execution_note: |
|
|
22
|
+
ROLLBACK INVOKED then RE-EXECUTED.
|
|
23
|
+
Initial run: `npm run lint` (tsc --noEmit) produced 2 TypeScript errors:
|
|
24
|
+
- src/search/__tests__/indexer.test.ts(22,16): error TS2339: Property 'create'
|
|
25
|
+
does not exist on type 'Mock<Procedure>'
|
|
26
|
+
- src/search/__tests__/query-engine.test.ts(26,16): same error
|
|
27
|
+
Root cause: both test files assigned `EmbedderMock.create = vi.fn()...` after
|
|
28
|
+
constructing with `vi.fn()`, which TypeScript does not recognize as a valid
|
|
29
|
+
property assignment on `Mock<Procedure>`.
|
|
30
|
+
Fix: replaced assignment pattern with `Object.assign(vi.fn()..., { create: vi.fn()... })`
|
|
31
|
+
in both test files. Re-run: `npm run lint` exits 0.
|
|
32
|
+
validation_result: "PASSED — tsc --noEmit exits 0"
|
|
33
|
+
rollback_executed: true
|
|
34
|
+
rollback_result: "Fixed TS2339 errors in indexer.test.ts and query-engine.test.ts"
|
|
35
|
+
duration_ms: 0
|
|
36
|
+
|
|
37
|
+
- step_id: "6.2"
|
|
38
|
+
status: "SUCCESS"
|
|
39
|
+
pre_condition: "PASSED"
|
|
40
|
+
constraints_checked: true
|
|
41
|
+
execution_note: |
|
|
42
|
+
`npm test` (vitest run) completed: 29 test files, 345 tests passed, 6 skipped.
|
|
43
|
+
No unexpected failures. All existing tests remain green after auth changes.
|
|
44
|
+
validation_result: "PASSED — npm test exits 0"
|
|
45
|
+
rollback_executed: false
|
|
46
|
+
rollback_result: ""
|
|
47
|
+
duration_ms: 0
|
|
48
|
+
|
|
49
|
+
- step_id: "6.3"
|
|
50
|
+
status: "SUCCESS"
|
|
51
|
+
pre_condition: "PASSED — AuthManager (step 2.1) exists at src/core/auth/manager.ts"
|
|
52
|
+
constraints_checked: true
|
|
53
|
+
execution_note: |
|
|
54
|
+
Created src/core/auth/__tests__/manager.test.ts with 13 unit tests covering:
|
|
55
|
+
- saveCredentials: writes JSON, creates parent dirs, sets 0600 permissions
|
|
56
|
+
- loadCredentials: returns parsed data, null on missing file, null on invalid JSON,
|
|
57
|
+
null on missing required fields
|
|
58
|
+
- isLoggedIn: true with refresh_token, false without file, false on empty token
|
|
59
|
+
- clearCredentials: deletes file, no-throw when missing
|
|
60
|
+
- getAccessToken: throws auth_not_logged_in when no credentials
|
|
61
|
+
Uses real temp directory (os.tmpdir()) with afterEach cleanup — no fs mocking.
|
|
62
|
+
validation_result: "PASSED — npx vitest run src/core/auth/__tests__/manager.test.ts exits 0 (13/13)"
|
|
63
|
+
rollback_executed: false
|
|
64
|
+
rollback_result: ""
|
|
65
|
+
duration_ms: 0
|
|
66
|
+
|
|
67
|
+
- step_id: "6.4"
|
|
68
|
+
status: "SUCCESS"
|
|
69
|
+
pre_condition: "PASSED — startLoopbackServer (step 2.2) exists at src/core/auth/server.ts"
|
|
70
|
+
constraints_checked: true
|
|
71
|
+
execution_note: |
|
|
72
|
+
Created src/core/auth/__tests__/server.test.ts with 5 unit tests covering:
|
|
73
|
+
- Port binding: server starts and resolves with credentials
|
|
74
|
+
- Timeout: server rejects after 120s (fake timers via vi.useFakeTimers + vi.advanceTimersByTimeAsync)
|
|
75
|
+
- Callback: resolves with credentials for valid code + state
|
|
76
|
+
- CSRF state validation (F-002): 403 returned for wrong state, getToken NOT called
|
|
77
|
+
- Cleanup: server is closed after successful callback
|
|
78
|
+
Mocked node:crypto randomBytes to yield deterministic FIXED_STATE for tests.
|
|
79
|
+
Mocked `open` to prevent browser launch.
|
|
80
|
+
Fixed unhandled-rejection warning in timeout test by attaching .catch() before timer advance.
|
|
81
|
+
validation_result: "PASSED — npx vitest run src/core/auth/__tests__/server.test.ts exits 0 (5/5)"
|
|
82
|
+
rollback_executed: false
|
|
83
|
+
rollback_result: ""
|
|
84
|
+
duration_ms: 0
|
|
85
|
+
|
|
86
|
+
- step_id: "6.5"
|
|
87
|
+
status: "SUCCESS"
|
|
88
|
+
pre_condition: "PASSED — Embedder.create() (step 4.1) exists at src/search/embedder.ts"
|
|
89
|
+
constraints_checked: true
|
|
90
|
+
execution_note: |
|
|
91
|
+
Created src/search/__tests__/embedder-auth.test.ts with 4 integration tests covering:
|
|
92
|
+
- API key priority: GEMINI_API_KEY set → authManager not consulted
|
|
93
|
+
- OAuth fallback: env key unset, isLoggedIn=true → getAccessToken called
|
|
94
|
+
- Neither available: env key unset, isLoggedIn=false → throws search_api_key_missing
|
|
95
|
+
with message mentioning both GEMINI_API_KEY and arc auth login
|
|
96
|
+
- No authManager: passes null → throws search_api_key_missing
|
|
97
|
+
Fixed @google/generative-ai mock to use regular function (not arrow) as constructor.
|
|
98
|
+
validation_result: "PASSED — npx vitest run src/search/__tests__/embedder-auth.test.ts exits 0 (4/4)"
|
|
99
|
+
rollback_executed: false
|
|
100
|
+
rollback_result: ""
|
|
101
|
+
duration_ms: 0
|
|
102
|
+
|
|
103
|
+
session_state:
|
|
104
|
+
blueprint_version: "2.0-HARDENED"
|
|
105
|
+
last_executed_phase: 6
|
|
106
|
+
completed_steps:
|
|
107
|
+
- "1.1"
|
|
108
|
+
- "1.2"
|
|
109
|
+
- "1.3"
|
|
110
|
+
- "1.4"
|
|
111
|
+
- "2.1"
|
|
112
|
+
- "2.2"
|
|
113
|
+
- "3.1"
|
|
114
|
+
- "3.2"
|
|
115
|
+
- "3.5"
|
|
116
|
+
- "4.1"
|
|
117
|
+
- "4.2"
|
|
118
|
+
- "4.3"
|
|
119
|
+
- "4.4"
|
|
120
|
+
- "4.5"
|
|
121
|
+
- "4.6a"
|
|
122
|
+
- "4.6b"
|
|
123
|
+
- "4.6c"
|
|
124
|
+
- "5.1"
|
|
125
|
+
- "6.1"
|
|
126
|
+
- "6.2"
|
|
127
|
+
- "6.3"
|
|
128
|
+
- "6.4"
|
|
129
|
+
- "6.5"
|
|
130
|
+
failed_step: ""
|
|
131
|
+
is_phase_complete: true
|
|
132
|
+
next_phase: null
|
|
133
|
+
|
|
134
|
+
state_delta:
|
|
135
|
+
files_created:
|
|
136
|
+
- "src/core/auth/__tests__/manager.test.ts"
|
|
137
|
+
- "src/core/auth/__tests__/server.test.ts"
|
|
138
|
+
- "src/search/__tests__/embedder-auth.test.ts"
|
|
139
|
+
files_modified:
|
|
140
|
+
- "src/search/__tests__/indexer.test.ts"
|
|
141
|
+
- "src/search/__tests__/query-engine.test.ts"
|
|
142
|
+
files_deleted: []
|
|
143
|
+
configs_changed: []
|
|
144
|
+
other_changes: []
|
|
145
|
+
|
|
146
|
+
post_mortem: {}
|
|
147
|
+
|
|
148
|
+
phase_summary:
|
|
149
|
+
total_steps_in_phase: 5
|
|
150
|
+
executed: 5
|
|
151
|
+
skipped: 0
|
|
152
|
+
succeeded: 5
|
|
153
|
+
failed: 0
|
|
154
|
+
system_stable: true
|
|
155
|
+
ready_for_next_phase: false
|
|
156
|
+
next_phase_number: null
|
|
157
|
+
handoff_message: |
|
|
158
|
+
Phase 6 complete — all validation and testing steps succeeded.
|
|
159
|
+
TypeScript compiles clean (0 errors). Full suite: 32 files, 367 tests pass.
|
|
160
|
+
New test coverage: AuthManager (13 tests), loopback server (5 tests, CSRF F-002 covered),
|
|
161
|
+
Embedder credential chain (4 tests). Blueprint task auth-oauth-login is fully complete.
|
|
162
|
+
No further phases remain.
|